/**
 * @file		adc.c
 * @brief		A/D Convertor
 * @note		None
 * @attention	None
 * 
 * <B><I>ALL RIGHTS RESERVED, COPYRIGHT&copy; SOCIONEXT INCORPORATED 2016</I></B>
 */

/* Standard includes. */
#include <stdio.h>

#include "dd_arm.h"

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"

#include "dd_pmc_adc.h"
#include "adc.h"

#include "ipcu.h"

// Notification enable.
static volatile int iAdcNotificationEnable = 0;

// Buffer data.
static volatile T_ADC_BUFFER xBuffer = { 0, 0, 0, 0 };

// Local function.
static void prvAdcCallback(void);
static void prvAdcCallbackDma(unsigned long *address);


/**
ADC callback function.
*/
static void prvAdcCallback(void)
{
	BaseType_t		xHigherPriorityTaskWoken, xResult;
	unsigned long	ulData[3];
	
	// Notification.
	if (iAdcNotificationEnable) {
		xHigherPriorityTaskWoken = pdFALSE;
		
		// Set ADC Notify data
		ulData[0] = 0x02000009;
		ulData[1] = 0;
		ulData[2] = 0;
		vIPCUSetNotifyCommand(ulData, 3);
		
		// Set Event Flag : ADC Notification.
		xResult = xEventGroupSetBitsFromISR(xNotifyEventFlag, (1 << NFY_ADC), &xHigherPriorityTaskWoken);
		if (xResult == pdPASS) {
			portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
		}
	}
}

static void prvAdcCallbackDma(unsigned long *address)
{
	BaseType_t		xHigherPriorityTaskWoken, xResult;
	unsigned long	ulData[3];
	
	// write pointer
	if (xBuffer.pointer != 0) {
		*((unsigned long*)xBuffer.pointer) = *address;
	}
	
	if ((*address + 0x20) >= (xBuffer.address + xBuffer.size)) {
		// Notification.
		if (iAdcNotificationEnable) {
			xHigherPriorityTaskWoken = pdFALSE;
			
			// Set ADC Notify data
			ulData[0] = 0x02000009;
			ulData[1] = 0;
			ulData[2] = 0;
			vIPCUSetNotifyCommand(ulData, 3);
			
			// Set Event Flag : ADC Notification.
			xResult = xEventGroupSetBitsFromISR(xNotifyEventFlag, (1 << NFY_ADC), &xHigherPriorityTaskWoken);
			if (xResult == pdPASS) {
				portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
			}
		}
		
		switch (xBuffer.overlap) {
			case 0:		// 0:no-overlap
				// ADC Stop
				Dd_PMC_ADC_Stop();
				break;
			case 1:		// 1:cyclic
				// Re-set start address.
				*address = xBuffer.address;
				break;
			default:	// 2:same position
				break;
		}
	}
	else {
		*address += 0x20;
	}
}


/**
A/D Convertor start.
@retval  0 : success
*/
int iAdcStart(void)
{
	T_DD_PMC_ADC_CTRL adc_ctrl;
	
	// Initialize.
	Dd_PMC_ADC_Init();
	
	// Set ADC Control.
	adc_ctrl.ch				= 0x00FF;		// ch0 - ch7
	adc_ctrl.cnv_mode		= E_DD_PMC_ADC_CONV_MODE_SINGLE;
	adc_ctrl.start_trig		= E_DD_PMC_ADC_START_TRIG_TIMER;
	adc_ctrl.timer_trig		= E_DD_PMC_ADC_TIMER_TRIG_0;
	adc_ctrl.tsel			= E_DD_PMC_ADC_TSEL_TOGGLE;
	adc_ctrl.ext_edge		= E_DD_PMC_ADC_EXT_TRIG_EDGE_RISE;
	adc_ctrl.sampling_time	= 0xFF;
	adc_ctrl.powerdown		= 1;
	adc_ctrl.cmp_data		= 0x03FF;
	Dd_PMC_ADC_Ctrl(&adc_ctrl);
	
	// Set Timer.
	Dd_PMC_ADC_Set_Timer(1000);		// Conversion cycle time (Provisional value) : 1000[usec].
	
	if ((xBuffer.address != 0) && (xBuffer.size != 0)) {
		// Set DMA.
		Dd_PMC_ADC_Set_DMA_Transfer(xBuffer.address);
		
		// ADC Start.
		Dd_PMC_ADC_Start((VP_CALLBACK)prvAdcCallbackDma);
	}
	else {
		// ADC Start.
		Dd_PMC_ADC_Start((VP_CALLBACK)prvAdcCallback);
	}
	
	// normal end.
	return 0;
}

/**
A/D Convertor stop.
@retval  0 : success
*/
int iAdcStop(void)
{
	// ADC Stop.
	Dd_PMC_ADC_Terminate();
	
	// normal end.
	return 0;
}

/**
A/D Convertor notification.
@param [in]	enable		notification enable.<br>
						0:disable, 1:enable
@retval  0 : success
@retval  1 : parameter error
*/
int iAdcNotification(int enable)
{
	if ((enable != 0) && (enable != 1)) {
		// parameter error.
		return  1;
	}
	
	// Set notification.
	iAdcNotificationEnable = enable;
	
	// normal end.
	return 0;
}

/**
A/D Convertor notification.
@param [in]	buff		Buffer data.
@retval  0 : success
@retval  1 : parameter error
*/
int iAdcSetBuffer(T_ADC_BUFFER const* const buff)
{
	if (buff == 0) {
		// parameter error.
		return 1;
	}
	
	xBuffer = *buff;
	
	// normal end.
	return 0;
}

